# Импорт библиотек
import pandas as pd
import numpy as np
import random
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as st
import scipy as sp
pd.options.mode.chained_assignment = None # default='warn
Важно определить, какие технические показатели качества связи сильнее всего влияют на удовлетворённость клиентов, и в первую очередь направить ресурсы на работу с ними.
data = pd.read_csv('megafon.csv') # Загрузка данных
data.head(3) # Cтруктура данных
| user_id | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming Download Throughput(Kbps) | Video Streaming xKB Start Delay(ms) | Web Page Download Throughput(Kbps) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 5 | NaN | 775.48846 | 360.13 | 86.56 | 3.93 | 1859.15 | 2309 | 1007.82 | 83 |
| 1 | 2 | 5 | 4 | 861.96324 | 3023.54 | 411.18 | 1.27 | 667.47 | 2080 | 255.36 | 425 |
| 2 | 3 | 1 | 4 | 261.11860 | 790.96 | 34.20 | 1.79 | 1079.60 | 6367 | 535.85 | 485 |
user_id — идентификатор абонента;
Q1 — ответ на первый вопрос В ходе опроса компания «Мегафон» предложила своим клиентам оценить уровень удовлетворённости качеством связи по десятибалльной шкале (где 10 — это «отлично», а 1 — «ужасно»). Если клиент оценивал качество связи на 9 или 10 баллов, опрос заканчивался;
Q2 - ответ на второй вопрос ; Если клиент ставил оценку ниже 9, задавался второй вопрос — о причинах неудовлетворённости качеством связи с предоставленными пронумерованными вариантами ответа. Ответ можно было дать в свободном формате или перечислить номера ответов через запятую:
Total Traffic(MB) — объем трафика передачи данных, насколько активно абонент использует мобильный интернет;
Downlink Throughput(Kbps) — средняя скорость «к абоненту», считается по всему трафику передачи данных;
Uplink Throughput(Kbps)— средняя скорость «от абонента», считается по всему трафику передачи данных;
Downlink TCP Retransmission Rate(%) — частота переотправок пакетов «к абоненту», чем выше, тем хуже. Если в канале возникает ошибка, пакет переотправляется. Снижается полезная скорость;
Video Streaming Download Throughput(Kbps) — скорость загрузки потокового видео, чем выше, тем лучше — меньше прерываний и лучше качество картинки;
Video Streaming xKB Start Delay(ms) — задержка старта воспроизведения видео, cколько времени пройдёт между нажатием на кнопку Play и началом воспроизведения видео. Чем меньше это время, тем быстрее начинается воспроизведение;
Web Page Download Throughput(Kbps) — скорость загрузки web-страниц через браузер, чем выше, тем лучше;
Web Average TCP RTT(ms) — пинг при просмотре web-страниц, чем меньше, тем лучше — быстрее загружаются web-страницы.
Первый технический показатель представлен как сумма за период в одну неделю перед участием в опросе. Остальные технические показатели отображают среднее значение по данному признаку за период в одну неделю перед участием в опросе.
data.info() # Непустые значения в колонках
<class 'pandas.core.frame.DataFrame'> RangeIndex: 3112 entries, 0 to 3111 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 3112 non-null int64 1 Q1 3110 non-null object 2 Q2 1315 non-null object 3 Total Traffic(MB) 3112 non-null float64 4 Downlink Throughput(Kbps) 3112 non-null float64 5 Uplink Throughput(Kbps) 3112 non-null float64 6 Downlink TCP Retransmission Rate(%) 3112 non-null float64 7 Video Streaming Download Throughput(Kbps) 3112 non-null float64 8 Video Streaming xKB Start Delay(ms) 3112 non-null int64 9 Web Page Download Throughput(Kbps) 3112 non-null float64 10 Web Average TCP RTT(ms) 3112 non-null int64 dtypes: float64(6), int64(3), object(2) memory usage: 267.6+ KB
Работа с переменной Q1 - ответ на первый вопрос, возможные значения 1-10
data['Q1'].value_counts() # Имеющиеся значения и их количество
10 846 1 532 3 325 8 291 9 238 5 234 7 200 2 168 4 123 6 101 0 10 1, 3 2 Нет 2 5, 6 2 Да 2 ОЦЕНКА-3/НЕВАЖНО/ 1 3 - дер.Ширяево Волоколамского района, 9 - в Москве 1 19 1 3, 9 1 Без з 1 10, 5 1 20, 89031081392 1 3, 7 1 0, 1, 5 1 11 1 ***** ** *** 1 Поохое 1 4. Тульская область Заокский район. Романовские дачи связи почти нет 1 10, 50 1 ? 1 Очень хорошо. Обслуживания я довольно. Спасибо вам.555 1 Когда в Москве-10 а когда в калужской области в деревне Бели-1 1 15 1 5, 7 1 Пока не понял 1 Hi 1 Чем даль ше,тем лучше.Спасибо за ваш труд.Оценка 10 ! 1 10, 9 1 Я в Смол. Области живу сейчас, не пользуюсь телефоном совсем 1 Ужасно 1 3 тройка, связь отвратительная, жалко платить за такой тарив 1 2, 9 1 Чдтчдтччдтччч 1 2, 5 1 Отвратительно 1 1, 8 1 1, 6 1 Я ценой услуг не удовлетворен 1 Немагу дать атценку денги незашто снимаеть скоро выклучаю 1 Name: Q1, dtype: int64
data['user_id'] = data['user_id'].astype(str)
# Удалим ошибочные и пропущенные значения с ответом на первый вопрос
data['Q1'] = data['Q1'].astype('str')
data['Q1'] = [elem if elem in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
else np.NaN for elem in data['Q1']]
data.dropna(subset=['Q1'], inplace=True)
data['Q1'].value_counts()
10 846 1 532 3 325 8 291 9 238 5 234 7 200 2 168 4 123 6 101 Name: Q1, dtype: int64
Разделим абонентов на группы и создадим метки группы для всех абонентов - A: оценка (9-10), B: оценка (5-8), C: оценка (1-4)
data['Group'] = data['Q1']
data['Group'] = ['A' if elem in ['9', '10'] else 'B' if elem
in ['5', '6', '7', '8'] else 'C' for elem in data['Group']]
data['Group'].value_counts() # Количество абонентов по группам
C 1148 A 1084 B 826 Name: Group, dtype: int64
# Изменим порядок колонок в датасете
cols = ['user_id', 'Group', 'Q1', 'Q2', 'Total Traffic(MB)',
'Downlink Throughput(Kbps)', 'Uplink Throughput(Kbps)',
'Video Streaming Download Throughput(Kbps)', 'Web Page Download Throughput(Kbps)',
'Downlink TCP Retransmission Rate(%)', 'Video Streaming xKB Start Delay(ms)',
'Web Average TCP RTT(ms)']
data = data[cols]
data.head(1)
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | B | 5 | NaN | 775.48846 | 360.13 | 86.56 | 1859.15 | 1007.82 | 3.93 | 2309 | 83 |
# Т.к. отсутствующие значения остались только в колонке Q2, проставим ответ 6 (затрудняюсь ответить) для пустых значений
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 3058 entries, 0 to 3111 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 3058 non-null object 1 Group 3058 non-null object 2 Q1 3058 non-null object 3 Q2 1315 non-null object 4 Total Traffic(MB) 3058 non-null float64 5 Downlink Throughput(Kbps) 3058 non-null float64 6 Uplink Throughput(Kbps) 3058 non-null float64 7 Video Streaming Download Throughput(Kbps) 3058 non-null float64 8 Web Page Download Throughput(Kbps) 3058 non-null float64 9 Downlink TCP Retransmission Rate(%) 3058 non-null float64 10 Video Streaming xKB Start Delay(ms) 3058 non-null int64 11 Web Average TCP RTT(ms) 3058 non-null int64 dtypes: float64(6), int64(2), object(4) memory usage: 310.6+ KB
data[data['Group'] != 'A'] = data[data['Group'] != 'A'].fillna('6')
data[(data['Q1'] == '9') | (data['Q1'] == '10')][['Q2']].shape[0] # Проверим количество строк группы A
1084
data[(data['Q1'] == '9') | (data['Q1'] == '10')][['Q2']].isna().sum()[0]
# Проверим пустые значения группы A на Q2 - совпадает
1084
data_Q1 = data.copy()
Построение доверительных интервалов для оценки долей пользователей, которые полностью довольны качеством связи (группа A), и для пользователей, которые неудовлетворёны качеством связи (группы В и С)
Оценка данного показателя позволяет оценить общую картину удовлетворенности услугами компании "Мегафон" с целью дальнейшего анализа конкретных групп и показателей
data_Q1[['user_id']].duplicated().sum() # Количество повторяющихся пользователей
0
df1 = data_Q1.copy()
df1['Group'] = ['Satisfied' if elem == 'A' else 'Dissatisfied' for elem in df1['Group']]
df1 = df1.groupby(['Group'])[['user_id']].count()
px.bar(df1, color=df1.index,
title='Количество пользователей в группах удовлетворенности услугами')
Расчет доверительного интервала для доли пользователей полностью удовлетворенными качеством связи (Ответ 9,10 на первый вопрос) генеральной совокупности пользователей компании 'Мегафон'
n = data_Q1.shape[0] # Размер выборки
n
3058
p = data_Q1[data_Q1['Group'] == 'A'].shape[0] / n
se = np.sqrt(p * (1 - p) / n)
np.round(st.norm.interval(0.95, loc=p, scale=se), 4)
array([0.3375, 0.3714])
Расчет доверительного интервала для доли пользователей неудовлетворенными качеством связи (Ответ 1-8 на первый вопрос) генеральной совокупности пользователей компании 'Мегафон'
n = data_Q1.shape[0]
p = data_Q1[data_Q1['Group'] != 'A'].shape[0] / n
se = np.sqrt(p * (1 - p) / n)
np.round(st.norm.interval(0.95, loc=p, scale=se), 4)
array([0.6286, 0.6625])
С вероятностью в 95% доля пользователей полностью удовлетворенными качеством связи компании 'Мегафон' лежит в промежутке (0.3375; 0.3714), доля пользоваталей неудовлетворенными качеством связи лежит в промежутке (0.6286; 0.6625)
Можем сделать вывод, что примерно 2/3 пользователей имеют некоторые проблемы со связью, следовательно нужно подробнее разбирать группы пользователей B и C
Построение доверительного интервала для оценки доли пользователей, которые указали на проблемы по связью (ответы: 1,2,3; на второй вопрос), относительно всех пользователей
Рассмотрение данного показателя в первую очередь важно, т.к. компания специализуруется на услугах голосовой связи и большинство пользователей указали на проблему именно с данной составляющей бизнеса
# Cоздание массива, разделенного по Q2, с наличием выбросов в числовых признаках для расчета доли
df2 = data_Q1.copy()
# Разделение нескольких ответов на список ответов
df2['Q2'] = df2['Q2'].str.replace(" ", "").str.split(',')
df2 = df2.explode('Q2') # Q2 - ответ на первый вопрос, возможные значения 1-7, или несколько ответов
df2['Q2'] = [elem if elem in ['1', '2', '3', '4', '5', '6', '7', np.NaN]
else 'error' for elem in df2['Q2']]
df2 = df2[df2['Q2'] != 'error']
# Заполним пропуски в Q2 для групп B и C нейтральным ответов
df2[df2['Group'] != 'A'] = df2[df2['Group'] != 'A'].fillna('6')
df2 = df2.reset_index(drop=True)
df2_1 = df2[(df2['Q2'] == '1') | (df2['Q2'] == '2') | (df2['Q2'] == '3')] # Интересующие нас группы
df2_1 = df2_1.drop_duplicates(subset='user_id') # Устранение повторяющихся пользователей внутри групп
df2_1
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3 | 4 | B | 8 | 3 | 179.18564 | 2590.97 | 325.88 | 7053.81 | 1221.02 | 0.80 | 3218 | 51 |
| 4 | 5 | C | 2 | 2 | 351.99208 | 731.61 | 223.54 | 4550.38 | 2336.56 | 1.15 | 1767 | 68 |
| 11 | 9 | C | 1 | 1 | 783.64464 | 1786.99 | 271.77 | 6802.42 | 1837.02 | 0.84 | 1200 | 132 |
| 14 | 10 | C | 3 | 1 | 455.97369 | 610.43 | 81.86 | 1317.76 | 1054.15 | 4.10 | 3350 | 165 |
| 18 | 11 | C | 3 | 1 | 526.08652 | 535.54 | 208.67 | 2621.14 | 2376.50 | 1.46 | 1479 | 88 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 4206 | 3103 | B | 8 | 1 | 413.99008 | 908.59 | 215.83 | 9449.73 | 2212.11 | 1.62 | 1467 | 51 |
| 4208 | 3105 | C | 1 | 1 | 206.28181 | 105.24 | 65.84 | 349.04 | 1035.69 | 3.86 | 2094 | 267 |
| 4213 | 3108 | C | 3 | 1 | 519.96475 | 1045.70 | 44.61 | 4523.66 | 1044.66 | 0.47 | 1468 | 304 |
| 4216 | 3109 | C | 3 | 1 | 171.52629 | 670.32 | 40.94 | 1711.54 | 954.91 | 2.35 | 2780 | 251 |
| 4221 | 3111 | B | 6 | 1 | 827.74515 | 1841.90 | 373.53 | 5675.93 | 2361.88 | 1.21 | 1905 | 202 |
1028 rows × 12 columns
n = data_Q1.shape[0] # Размер выборки
n
3058
p = df2_1.shape[0] / n
se = np.sqrt(p * (1 - p) / n)
np.round(st.norm.interval(0.95, loc=p, scale=se), 4)
array([0.3194, 0.3529])
С вероятностью в 95% доля пользователей, которые указали на проблемы по связью (ответы: 1,2,3; на второй вопрос), относительно всех пользователей компании 'Мегафон' лежит в промежутке (0.3194, 0.3529])
# for i in ['1', '2', '3', '4', '5', '6', '7']:
# df_inter = df2[(df2['Q2'] == i)]
# df_inter = df_inter.drop_duplicates(subset='user_id')
# p = df_inter.shape[0] / n
# se = np.sqrt(p * (1 - p) / n)
# print('Доверительный интервал для доли пользователей, ответивших на Q2 -', i,
# np.round(st.norm.interval(0.95, loc=p, scale=se), 4))
Устранение выбросов в числовых признаках
Следуя эвристики, что выбросы находятся за пределами следующих интервалов: Q1–1.5 x IQR и Q3 + 1.5 x IQR, преобразуем данные.
Q1 - Первый квартиль, равен 25-ому процентилю;
Q3 - Третий квартиль, равен 75-ому процентилю;
IQR - число, которое показывает разброс средней половины (т.е. средние 50%) набора данных и помогает определить выбросы, разница между Q3 и Q1;
col = data.describe().columns # Колонки числовых атрибутов
Q1, Q3 = data_Q1[col].quantile(0.25), data_Q1[col].quantile(0.75)
IQR = Q3 - Q1
data_Q1_Out = data_Q1[~((data_Q1[col] < (Q1 - 1.5 * IQR)) | (data_Q1[col] >
(Q3 + 1.5 * IQR))).any(axis=1)]
data = data_Q1_Out
Работа с переменной Q2 - ответ на первый вопрос, возможные значения 1-7, или несколько ответов
data.Q2.isna().sum()
700
data['Q2'].value_counts()
6 451 3 135 4 100 1 98 1, 3 80 3, 4 65 1, 3, 4 43 7 39 1, 2, 3 31 1, 4 29 1, 3, 4, 5 28 3, 4, 5 27 1, 2, 3, 4, 5 19 1, 2 16 4, 5 16 1, 4, 5 13 1, 2, 3, 4 13 1, 2, 4 7 2, 3, 4 7 2, 3 7 3, 5 7 2 6 1, 5 5 5 4 1, 4, 7 3 2, 4 3 2, 3, 4, 5 3 1, 3, 5 3 1, 2, 4, 5 2 1, 3, 4, 5, 7 2 2, 4, 5 2 1, 2, 5 2 1, 3, 4, 7 2 0, 05, 2, 27, 7 1 3, 7 1 0, 1, 7 1 0 1 1, 3, 7 1 1, 2, 7 1 0, 3 1 1, 2, 3, 7 1 1, 2, 34 1 1, 2, 3, 5 1 1, 2, 3, 4, 5, 6 1 3, 4, 5, 7 1 3, 4, 7 1 Name: Q2, dtype: int64
# Для наблюдений с несколькими вариантами, уберем пробелы, переведем в списки и разобьем на отдельные наблюдения.
# Значения остальных атрибутов для таких наблюдений остаются неизменными.
data['Q2'] = data['Q2'].str.replace(" ", "").str.split(',')
data = data.explode('Q2')
data['Q2'] = [elem if elem in ['1', '2', '3', '4', '5', '6', '7', np.NaN]
else 'error' for elem in data['Q2']]
data = data[data['Q2'] != 'error']
data['Q2'].value_counts() #Финальное количество вариантов ответа на Q2
3 480 6 452 1 403 4 387 5 136 2 124 7 54 Name: Q2, dtype: int64
# Заполним пропуски в Q2 для групп B и C нейтральным ответов
data[data['Group'] != 'A'] = data[data['Group'] != 'A'].fillna('6')
data = data.reset_index(drop=True)
data_Q2 = data.copy()
Рассмотрим данные для минимальных и максимальных показателей каждого признака с соответствующими значениями других признаков
datamin = pd.DataFrame(columns=data.columns)
datamax = pd.DataFrame(columns=data.columns)
# Создадим копию данных и исключим ошибочные минимальные (нулевые) значения признаков
data_copy = data.copy()
data_copy = data_copy[(data_copy['Video Streaming Download Throughput(Kbps)'] > 0) &
(data_copy['Web Page Download Throughput(Kbps)'] > 0) &
(data_copy['Web Average TCP RTT(ms)'] > 0)]
for column in data[col]:
datamin = datamin.append(data_copy[data_copy[column] == data_copy[column].min()])
datamax = datamax.append(data_copy[data_copy[column] == data_copy[column].max()])
# Исключим дубликаты из полученных датафреймов
datamin = datamin.drop_duplicates(subset='Total Traffic(MB)')
datamax = datamax.drop_duplicates(subset='Total Traffic(MB)')
# datamin.append(data_Q1_Out.describe().loc[['mean'], :], sort=False)
# datamax.append(data_Q1_Out.describe().loc[['mean'], :], sort=False)
Рассмотрим меры центральной тенденции - среднее и медиану для групп пользователей разделенных по ответу на первый вопрос:
Группы - A: оценка (9-10), B: оценка (5-8), C: оценка (1-4)
Q1_mean_1 = data_Q1_Out.groupby(['Group'])[['user_id']].count()
Q1_mean_1.columns = ['user_id, count']
Q1_mean_2 = data_Q1_Out.groupby(['Group']).agg('mean') \
.sort_values(by='Group').round(2)
Q1_mean = pd.concat([Q1_mean_1, Q1_mean_2], axis=1)
Q1_median_1 = data_Q1_Out.groupby(['Group'])[['user_id']].count()
Q1_median_1.columns = ['user_id, count']
Q1_median_2 = data_Q1_Out.groupby(['Group']).agg('median') \
.sort_values(by='Group').round(2)
Q1_median = pd.concat([Q1_median_1, Q1_median_2], axis=1)
Q1_mean # Средние для числовых показателей
| user_id, count | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|
| Group | |||||||||
| A | 700 | 412.30 | 1876.49 | 145.13 | 5371.52 | 1879.51 | 1.38 | 1696.58 | 131.57 |
| B | 547 | 424.04 | 1842.24 | 144.26 | 5260.61 | 1824.31 | 1.49 | 1768.55 | 138.85 |
| C | 734 | 416.21 | 1662.22 | 137.44 | 4708.80 | 1707.93 | 1.58 | 1855.18 | 149.55 |
Q1_median # Медианные значения для числовых показателей
| user_id, count | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|
| Group | |||||||||
| A | 700 | 365.00 | 1519.74 | 120.80 | 4821.22 | 1769.54 | 1.20 | 1572.5 | 105.5 |
| B | 547 | 389.35 | 1366.61 | 119.43 | 4623.44 | 1666.18 | 1.28 | 1655.0 | 114.0 |
| C | 734 | 377.32 | 1293.52 | 116.33 | 4109.82 | 1576.06 | 1.32 | 1694.0 | 127.5 |
Показатели: Total Traffic(MB) - из опрошенных мобильный интернет активнее всего используется пользователями группы 'B';
Downlink Throughput(Kbps),Uplink Throughput(Kbps), Video Streaming Download Throughput(Kbps), Web Page Download Throughput(Kbps) - наибольшие показатели скорости загрузки у пользователей группы 'A', наименьшие у группы 'C';
Downlink TCP Retransmission Rate(%)(частота переотправок пакетов «к абоненту»),Video Streaming xKB Start Delay(ms)(задержка старта воспроизведения видео),Web Average TCP RTT(ms)(пинг при просмотре web-страниц) - наименьшие (наилучшие) показатели у пользователей группы 'A', наибольшие (наихудшие) - у группы 'C';
В целом, основываясь на рассмотренных выборках, имеется предположение, что разница в показателях пользователей группы 'A' и группы 'B' различаются меньше, чем разница в показателях группы 'B' и 'C'.
Рассмотрим меры центральной тенденции - среднее и медиану для групп пользователей разделенных по ответу на второй вопрос:
1.Недозвоны, обрывы при звонках
2.Время ожидания гудков при звонке
3.Плохое качество связи в зданиях, тц и т.д.
4.Медленный мобильный интернет
5.Медленная загрузка видео
6.Затрудняюсь ответить
7.Свой вариант
Q2_mean_1 = data_Q2.groupby(['Q2'])[['user_id']].count()
Q2_mean_1.columns = ['user_id, count']
Q2_mean_2 = data_Q2.groupby(['Q2']).agg('mean') \
.sort_values(by='Q2').round(2)
Q2_mean = pd.concat([Q2_mean_1, Q2_mean_2], axis=1)
Q2_median_1 = data_Q2.groupby(['Q2'])[['user_id']].count()
Q2_median_1.columns = ['user_id, count']
Q2_median_2 = data_Q2.groupby(['Q2']).agg('median').sort_values(by='Q2').round(2)
Q2_median = pd.concat([Q2_median_1, Q2_median_2], axis=1)
Q2_mean # Средние для числовых показателей
| user_id, count | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|
| Q2 | |||||||||
| 1 | 403 | 410.06 | 1787.28 | 145.94 | 5045.35 | 1756.82 | 1.59 | 1817.25 | 145.68 |
| 2 | 124 | 395.59 | 1654.47 | 140.41 | 4793.18 | 1770.88 | 1.56 | 1847.86 | 153.37 |
| 3 | 480 | 420.52 | 1728.35 | 141.19 | 4817.77 | 1769.04 | 1.62 | 1809.03 | 142.59 |
| 4 | 387 | 440.76 | 1661.62 | 135.53 | 4482.69 | 1648.09 | 1.59 | 1902.98 | 154.15 |
| 5 | 136 | 434.34 | 1447.95 | 131.38 | 3919.86 | 1613.10 | 1.58 | 1929.76 | 157.15 |
| 6 | 452 | 412.84 | 1684.16 | 142.23 | 5047.90 | 1769.47 | 1.50 | 1796.79 | 146.30 |
| 7 | 54 | 406.87 | 1753.45 | 138.13 | 4808.98 | 1803.71 | 1.52 | 1817.15 | 131.33 |
Q2_median # Медианные значения для числовых показателей
| user_id, count | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|
| Q2 | |||||||||
| 1 | 403 | 370.92 | 1361.30 | 119.45 | 4576.39 | 1589.45 | 1.37 | 1662.0 | 125.0 |
| 2 | 124 | 375.97 | 1095.54 | 116.10 | 4050.38 | 1628.90 | 1.32 | 1709.5 | 131.5 |
| 3 | 480 | 379.86 | 1314.74 | 121.15 | 4252.07 | 1644.94 | 1.34 | 1664.5 | 117.0 |
| 4 | 387 | 415.86 | 1155.32 | 118.53 | 3915.89 | 1498.28 | 1.36 | 1743.0 | 133.0 |
| 5 | 136 | 413.47 | 1108.19 | 104.55 | 3287.10 | 1430.19 | 1.33 | 1743.5 | 135.5 |
| 6 | 452 | 366.52 | 1265.22 | 116.13 | 4349.46 | 1641.42 | 1.27 | 1671.0 | 122.0 |
| 7 | 54 | 352.47 | 1543.91 | 124.46 | 4223.85 | 1513.76 | 1.39 | 1700.5 | 101.0 |
На первый взгляд - видимых различий не наблюдается, более детальные различия и гипотезы между данными группами далее будут проверены в ходе исследования
Тесты по качеству голосовой связи фиксируют следующие параметры:
Качество голосовой связи. Включает в себя долю неуспешных попыток установления соединения, долю оборванных вызовов, разборчивость речи. Качество речи измеряется по технологии POLQA (Perceptual Objective Listening Quality Assessment), утверждённой Международным союзом электросвязи. Оценка каждого голосового вызова осуществляется на протяжении трех минут. Результатом обзвона «большой четверки» является совокупность log-файлов измерительных комплексов. Оценка каждого голосового вызова осуществляется на протяжении 3 минут.
Доставку sms-сообщений (доля недошедших до адресата сообщений, а также среднее время доставки).
Передачу данных: доля неуспешного TCP/IP соединения с сервером (HTTP IP-Service Access Failure Ratio); доля неуспешных сессий по протоколу HTTP (HTTP Session Failure Ratio); среднее значение скорости передачи данных к абоненту (HTTP DL Mean User Data Rate) в кбит/c; продолжительность успешной сессии (HTTP Session Time) в мс. Процедура выполняется для трёх стандартов: GSM (2G), UMTS (3G) и LTE (4G). Размер файла для загрузки по протоколу HTTP для GSM и UMTS составляет 3 МБ, а для LTE 100 МБ.
Общий скан покрытия, наличие и уровень сигнала (измеряемый в дБм).
Параметры, выделенные жирным шрифтом, имеются в представленных данных, следуют обратить на них внимание при исследованиее пользователей имеющих проблемы со связью (1.Недозвоны, обрывы при звонках; 2.Время ожидания гудков при звонке; 3.Плохое качество связи в зданиях, тц и т.д.)
Проверяем гипотезу относительно разницы средних показателей Downlink Throughput(Kbps)(средняя скорость «к абоненту»),Downlink TCP Retransmission Rate(%) (частота переотправок пакетов «к абоненту», чем выше, тем хуже.) между группами пользоваталей A и B
Группа A - пользователи, ответившие на второй вопрос (Q2) - 1,2,3: 1. Недозвоны, обрывы при звонках; 2. Время ожидания гудков при звонке; 3. Плохое качество связи в зданиях, тц и т.д Группа B - пользователи, ответившие на первый вопрос (Q1) - 9, 10 (отличное качество связи)
Берем наши выборки, делаем по каждой из них бутстреп и проверяем с помощью построения доверительных интервалов, пересекаются ли они, а также генерируем распределние разницы средних.
# Группа A
df2_2 = data_Q2[(data_Q2['Q2'] == '1') | (data_Q2['Q2'] == '2') | (data_Q2['Q2'] == '3')]
# Интересующие нас пользователи
df2_2 = df2_2.drop_duplicates(subset='user_id') # Устранение повторяющихся пользователей внутри групп
df2_2
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 4 | B | 8 | 3 | 179.18564 | 2590.97 | 325.88 | 7053.81 | 1221.02 | 0.80 | 3218 | 51 |
| 2 | 5 | C | 2 | 2 | 351.99208 | 731.61 | 223.54 | 4550.38 | 2336.56 | 1.15 | 1767 | 68 |
| 5 | 9 | C | 1 | 1 | 783.64464 | 1786.99 | 271.77 | 6802.42 | 1837.02 | 0.84 | 1200 | 132 |
| 8 | 10 | C | 3 | 1 | 455.97369 | 610.43 | 81.86 | 1317.76 | 1054.15 | 4.10 | 3350 | 165 |
| 12 | 11 | C | 3 | 1 | 526.08652 | 535.54 | 208.67 | 2621.14 | 2376.50 | 1.46 | 1479 | 88 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2720 | 3103 | B | 8 | 1 | 413.99008 | 908.59 | 215.83 | 9449.73 | 2212.11 | 1.62 | 1467 | 51 |
| 2721 | 3105 | C | 1 | 1 | 206.28181 | 105.24 | 65.84 | 349.04 | 1035.69 | 3.86 | 2094 | 267 |
| 2726 | 3108 | C | 3 | 1 | 519.96475 | 1045.70 | 44.61 | 4523.66 | 1044.66 | 0.47 | 1468 | 304 |
| 2729 | 3109 | C | 3 | 1 | 171.52629 | 670.32 | 40.94 | 1711.54 | 954.91 | 2.35 | 2780 | 251 |
| 2734 | 3111 | B | 6 | 1 | 827.74515 | 1841.90 | 373.53 | 5675.93 | 2361.88 | 1.21 | 1905 | 202 |
670 rows × 12 columns
# Группа B
df2_3 = data_Q1_Out[data_Q1_Out['Group'] == 'A']
df2_3 = df2_3.drop_duplicates(subset='user_id') # Устранение повторяющихся пользователей внутри групп
df2_3
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15 | 16 | A | 10 | NaN | 767.54725 | 1729.37 | 107.94 | 2490.93 | 2514.08 | 1.96 | 1660 | 92 |
| 21 | 22 | A | 10 | NaN | 601.06519 | 2135.92 | 120.55 | 7215.26 | 1014.48 | 1.73 | 1649 | 231 |
| 25 | 26 | A | 10 | NaN | 360.59845 | 3743.08 | 250.93 | 5943.66 | 2714.29 | 0.98 | 2029 | 61 |
| 27 | 28 | A | 10 | NaN | 373.37642 | 865.79 | 82.03 | 1913.33 | 1743.42 | 2.62 | 2638 | 122 |
| 28 | 29 | A | 10 | NaN | 232.99499 | 535.05 | 71.64 | 1332.35 | 3421.72 | 1.07 | 3548 | 110 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3091 | 3092 | A | 9 | NaN | 335.35499 | 1767.76 | 104.68 | 10115.44 | 462.42 | 0.38 | 1234 | 164 |
| 3092 | 3093 | A | 10 | NaN | 758.71809 | 1512.61 | 192.21 | 796.91 | 216.96 | 0.87 | 2567 | 160 |
| 3098 | 3099 | A | 10 | NaN | 343.79289 | 3469.50 | 237.13 | 4559.33 | 409.40 | 0.69 | 1725 | 150 |
| 3105 | 3106 | A | 10 | NaN | 424.34855 | 2258.16 | 150.21 | 5623.82 | 3480.14 | 1.56 | 1313 | 109 |
| 3109 | 3110 | A | 10 | NaN | 187.44936 | 590.29 | 186.36 | 3182.83 | 1094.62 | 2.06 | 2195 | 109 |
700 rows × 12 columns
mean_bc = df2_2[['Downlink Throughput(Kbps)', 'Downlink TCP Retransmission Rate(%)']].mean()[1]
df2_2[['Downlink Throughput(Kbps)', 'Downlink TCP Retransmission Rate(%)']].mean()
Downlink Throughput(Kbps) 1768.451985 Downlink TCP Retransmission Rate(%) 1.605731 dtype: float64
mean_a = df2_3[['Downlink Throughput(Kbps)', 'Downlink TCP Retransmission Rate(%)']].mean()[1]
df2_3[['Downlink Throughput(Kbps)', 'Downlink TCP Retransmission Rate(%)']].mean()
Downlink Throughput(Kbps) 1876.485714 Downlink TCP Retransmission Rate(%) 1.376914 dtype: float64
В виду того, что показатель Downlink Throughput(Kbps) — средняя скорость «к абоненту», считается по всему трафику передачи данных, т.е. основной трафик потребляет просмотр web-страниц и загрузка видео, также мы можем заметить, что рассматриваемое среднее выше в группе A, поэтому будем рассматривать показатель среднего Downlink TCP Retransmission Rate(%) — частота переотправок пакетов «к абоненту».
Рассматривается нулевая гипотеза о равенстве рассматриваемых средних Генеральных совокупностей Альтернативная гипотеза - рассматриваемые средние не равны (Предполагается что среднее группы B < среднее группы A, т.к. больше - хуже)
mean_diff = mean_bc - mean_a
mean_diff
0.22881705756929804
differences = np.zeros((1, 1500))
confidence = 0.95
count = 0
for i in range(0, 1500):
s1 = random.choices(df2_2['Downlink TCP Retransmission Rate(%)'].values, k=1000)
s2 = random.choices(df2_3['Downlink TCP Retransmission Rate(%)'].values, k=1000)
m1 = np.mean(s1)
m2 = np.mean(s2)
n = len(s1)
se1 = st.sem(s1)
se2 = st.sem(s2)
h1 = se1 * st.t._ppf((1+confidence)/2., n-1)
h2 = se2 * st.t._ppf((1+confidence)/2., n-1)
differences[0][i] = m1 - m2
l1, r1, l2, r2 = m1-h1, m1+h1, m2-h2, m2+h2 # Левая и правая граница интервалов
if (l2<l1<r2<r1) or (l1<l2<r2<r1) or (l2<l1<r1<r2) or (l1<l2<r1<r2): #пересечение интервалов
print(l1,r1,l2,r2) # Выводим значения интервалов, если они пересекаются
if (l2<r2<l1<r1):
count += 1 # Считаем число случаев, когда доверительный интервал группы B меньше,
#чем интервал группы A
print(count) # Число случаев, когда интервал группы B левее, чем интервал группы A
1.4855373244501806 1.6096226755498193 1.3848436190109525 1.4977763809890472 1.471705767806714 1.5944742321932857 1.363084720914124 1.4751352790858763 1.4255470465983786 1.5439129534016216 1.3824556886168688 1.493124311383131 1.4978461190439638 1.6207138809560362 1.3949687044622345 1.5068312955377652 1.4705413318630975 1.5923386681369027 1.3678791530086756 1.4756208469913246 1.4733441144979078 1.5939558855020926 1.3743149196260795 1.4857450803739198 1494
((differences - np.mean(differences)) >= mean_diff).sum()
# В скольких случаях значения центрированного вектора превосходит заданную разницу
0
P-value = 0 -> меньше чем любой разумный уровень значимости -> нулевая гипотеза о равенстве средних отклоняется -> Разница есть
df_diff = pd.DataFrame(differences).transpose() # Датафрейм разницы средних для построения графика
df_diff.columns = ['differences']
df_diff
| differences | |
|---|---|
| 0 | 0.27160 |
| 1 | 0.18757 |
| 2 | 0.20196 |
| 3 | 0.30405 |
| 4 | 0.17815 |
| ... | ... |
| 1495 | 0.19562 |
| 1496 | 0.21537 |
| 1497 | 0.24096 |
| 1498 | 0.28706 |
| 1499 | 0.21798 |
1500 rows × 1 columns
fig = px.histogram(df_diff, nbins = 50,
title='Распределение разницы средних для рассматриваемых групп')
fig.show() # Распределение близко к нормальному
На основании произведенных расчетов и тестов можем сделать вывод, что показатель Downlink TCP Retransmission Rate(%) — частота переотправок пакетов «к абоненту», статистически различается (значимо больше у пользователей рассматриваемой группы A (Q2-1,2,3) ГС, чем у пользоваталей, довольных качеством).
Проверяем гипотезу относительно разницы средних показателя Downlink Throughput(Kbps)(средняя скорость «к абоненту») между группами пользоваталей A и B
Группа A - пользователи, ответившие на второй вопрос (Q2) - 3: Плохое качество связи в зданиях, тц и т.д.; Группа B - пользователи, ответившие на второй вопрос (Q2) - 4:Медленный мобильный интернет
Берем наши выборки, делаем по каждой из них бутстреп и проверяем с помощью построения доверительных интервалов, пересекаются ли они, а также генерируем распределние разницы средних.
Рассматривается нулевая гипотеза о равенстве рассматриваемых средних Генеральных совокупностей Альтернативная гипотеза - рассматриваемые средние не равны
# Рассматриваются независимые наблюдения в двух группах
mass = data_Q2[(data_Q2['Q2'] == '3') | (data_Q2['Q2'] == '4')]
indexes = pd.DataFrame(mass['user_id'].value_counts())
indexes = indexes[indexes['user_id'] != 1].index
mass['user_id'] = [np.NaN if elem in indexes else elem for elem in mass['user_id']]
mass = mass.dropna()
mass
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 4 | B | 8 | 3 | 179.18564 | 2590.97 | 325.88 | 7053.81 | 1221.02 | 0.80 | 3218 | 51 |
| 13 | 11 | C | 3 | 3 | 526.08652 | 535.54 | 208.67 | 2621.14 | 2376.50 | 1.46 | 1479 | 88 |
| 16 | 19 | B | 7 | 3 | 811.55618 | 460.32 | 65.20 | 1583.27 | 1587.16 | 1.50 | 1340 | 57 |
| 31 | 50 | C | 2 | 3 | 453.12793 | 3325.86 | 167.74 | 11702.86 | 2455.82 | 0.59 | 1212 | 40 |
| 35 | 53 | B | 6 | 3 | 168.11047 | 2031.20 | 164.31 | 5234.92 | 1665.56 | 0.71 | 1838 | 95 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2705 | 3095 | C | 3 | 3 | 566.73519 | 3477.18 | 124.84 | 5427.74 | 2410.91 | 0.76 | 1188 | 145 |
| 2718 | 3101 | C | 2 | 4 | 248.99905 | 2347.42 | 309.08 | 5001.39 | 1837.79 | 1.64 | 1394 | 83 |
| 2719 | 3102 | B | 8 | 4 | 189.14150 | 2432.18 | 72.80 | 2152.91 | 1410.52 | 0.70 | 2719 | 289 |
| 2727 | 3108 | C | 3 | 4 | 519.96475 | 1045.70 | 44.61 | 4523.66 | 1044.66 | 0.47 | 1468 | 304 |
| 2735 | 3111 | B | 6 | 3 | 827.74515 | 1841.90 | 373.53 | 5675.93 | 2361.88 | 1.21 | 1905 | 202 |
443 rows × 12 columns
# Группа A
df3_2 = mass[(mass['Q2'] == '3')] # Интересующие нас пользователи
df3_2
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 4 | B | 8 | 3 | 179.18564 | 2590.97 | 325.88 | 7053.81 | 1221.02 | 0.80 | 3218 | 51 |
| 13 | 11 | C | 3 | 3 | 526.08652 | 535.54 | 208.67 | 2621.14 | 2376.50 | 1.46 | 1479 | 88 |
| 16 | 19 | B | 7 | 3 | 811.55618 | 460.32 | 65.20 | 1583.27 | 1587.16 | 1.50 | 1340 | 57 |
| 31 | 50 | C | 2 | 3 | 453.12793 | 3325.86 | 167.74 | 11702.86 | 2455.82 | 0.59 | 1212 | 40 |
| 35 | 53 | B | 6 | 3 | 168.11047 | 2031.20 | 164.31 | 5234.92 | 1665.56 | 0.71 | 1838 | 95 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2686 | 3067 | B | 5 | 3 | 136.63347 | 1272.06 | 140.17 | 2328.40 | 1248.35 | 3.16 | 2647 | 361 |
| 2687 | 3071 | B | 8 | 3 | 741.69656 | 498.88 | 73.80 | 1201.63 | 679.58 | 0.76 | 3633 | 251 |
| 2688 | 3073 | C | 2 | 3 | 358.79580 | 1604.98 | 223.12 | 8640.89 | 1601.76 | 1.08 | 1102 | 70 |
| 2705 | 3095 | C | 3 | 3 | 566.73519 | 3477.18 | 124.84 | 5427.74 | 2410.91 | 0.76 | 1188 | 145 |
| 2735 | 3111 | B | 6 | 3 | 827.74515 | 1841.90 | 373.53 | 5675.93 | 2361.88 | 1.21 | 1905 | 202 |
268 rows × 12 columns
# Группа B
df3_3 = mass[(mass['Q2'] == '4')]
df3_3
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 45 | 67 | C | 3 | 4 | 204.46809 | 1109.97 | 100.50 | 5360.84 | 1671.27 | 0.79 | 1402 | 136 |
| 108 | 129 | C | 1 | 4 | 641.85847 | 4410.52 | 357.93 | 6230.28 | 1877.49 | 1.86 | 1143 | 73 |
| 112 | 134 | B | 7 | 4 | 126.95619 | 3557.73 | 80.84 | 7237.32 | 4717.72 | 0.30 | 895 | 171 |
| 136 | 168 | C | 1 | 4 | 661.11584 | 1057.29 | 144.00 | 5493.31 | 1615.08 | 1.57 | 1346 | 77 |
| 164 | 202 | B | 8 | 4 | 825.33039 | 832.71 | 181.23 | 2164.55 | 929.74 | 3.65 | 2807 | 180 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2691 | 3077 | B | 5 | 4 | 144.43657 | 781.40 | 114.33 | 4721.98 | 2530.44 | 2.07 | 1608 | 118 |
| 2701 | 3091 | C | 2 | 4 | 775.73132 | 420.91 | 67.61 | 1981.25 | 658.33 | 2.31 | 2024 | 266 |
| 2718 | 3101 | C | 2 | 4 | 248.99905 | 2347.42 | 309.08 | 5001.39 | 1837.79 | 1.64 | 1394 | 83 |
| 2719 | 3102 | B | 8 | 4 | 189.14150 | 2432.18 | 72.80 | 2152.91 | 1410.52 | 0.70 | 2719 | 289 |
| 2727 | 3108 | C | 3 | 4 | 519.96475 | 1045.70 | 44.61 | 4523.66 | 1044.66 | 0.47 | 1468 | 304 |
175 rows × 12 columns
mean_bc_2 = df3_2[['Downlink Throughput(Kbps)']].mean()[0]
df3_2[['Downlink Throughput(Kbps)']].mean()
Downlink Throughput(Kbps) 1799.950075 dtype: float64
mean_a_2 = df3_3[['Downlink Throughput(Kbps)']].mean()[0]
df3_3[['Downlink Throughput(Kbps)']].mean()
Downlink Throughput(Kbps) 1690.419371 dtype: float64
mean_diff_2 = np.abs(mean_a_2 - mean_bc_2)
mean_diff_2
109.53070319829408
differences_2 = np.zeros((1, 1500))
confidence = 0.95
count = 0
for i in range(0, 1500):
s1 = random.choices(df3_2['Downlink Throughput(Kbps)'].values, k=1000)
s2 = random.choices(df3_3['Downlink Throughput(Kbps)'].values, k=1000)
m1 = np.mean(s1)
m2 = np.mean(s2)
n = len(s1)
se1 = st.sem(s1)
se2 = st.sem(s2)
h1 = se1 * st.t._ppf((1+confidence)/2., n-1)
h2 = se2 * st.t._ppf((1+confidence)/2., n-1)
differences_2[0][i] = m1 - m2
l1, r1, l2, r2 = m1-h1, m1+h1, m2-h2, m2+h2 # Левая и правая граница интервалов
if (l1<l2<r1<r2) or (l2<l1<r1<r2) or (l1<l2<r2<r1) or (l2<l1<r2<r1): #пересечение интервалов
count += 1 # Считаем число случаев пересечения
print(count)
1255
((np.abs(differences_2 - np.mean(differences_2)) >= mean_diff_2).sum()) / 1500
# Доля случаев когда центрированная разница больше изначально заданной
0.072
Полученное P-value больше чем P-value = 0.05 при заданном уровне значимости -> нет оснований отклонить нулевую гипотезу о равенстве средних
df_diff_2 = pd.DataFrame(differences_2 - np.mean(differences_2)).transpose()
# Датафрейм разницы средних для построения графика
df_diff_2.columns = ['differences']
df_diff_2
| differences | |
|---|---|
| 0 | 84.211306 |
| 1 | -64.294404 |
| 2 | 50.828856 |
| 3 | -65.149544 |
| 4 | 95.765746 |
| ... | ... |
| 1495 | -31.065244 |
| 1496 | 52.473516 |
| 1497 | -65.782074 |
| 1498 | -54.186344 |
| 1499 | 36.660706 |
1500 rows × 1 columns
fig = px.histogram(df_diff_2, nbins = 50,
title='Распределение разницы средних для рассматриваемых групп')
fig.show() # Распределение напоминает нормальное
mass.groupby(['Q2'])[['Downlink Throughput(Kbps)']] \
.agg(['count', 'mean']).round(2) \
.style.apply(lambda x: ['background: lightgreen' if x.name else '' for i in x], axis=1)
| Downlink Throughput(Kbps) | ||
|---|---|---|
| count | mean | |
| Q2 | ||
| 3 | 268 | 1799.950000 |
| 4 | 175 | 1690.420000 |
data_Q1_Out.groupby(['Group'])[['Downlink Throughput(Kbps)']] \
.agg(['count', 'mean']).round(2) \
.style.apply(lambda x: ['background: orange' if x.name == 'A' else '' for i in x], axis=1)
| Downlink Throughput(Kbps) | ||
|---|---|---|
| count | mean | |
| Group | ||
| A | 700 | 1876.490000 |
| B | 547 | 1842.240000 |
| C | 734 | 1662.220000 |
На основании произведенных расчетов и тестов можем сделать вывод, что показатель Downlink Throughput(Kbps)(средняя скорость «к абоненту»), статистически не различается у ГС пользователей ответивших на второй вопрос (Q2) - 3: Плохое качество связи в зданиях, тц и т.д. и пользователей, ответивших на второй вопрос (Q2) - 4: Медленный мобильный интернет
Следовательно, следует сравнить данный показатель между разными категориями пользователей - ответивших на вопрос Q2: 3,4 и пользователями ответивших на Q1: 9,10
Проверяем гипотезу относительно разницы средних показателя Downlink Throughput(Kbps)(средняя скорость «к абоненту») между группами пользоваталей A и B
Группа A - пользователи, ответившие на второй вопрос (Q2) - 3: Плохое качество связи в зданиях, тц и т.д.; Группа B - пользователи, ответившие на второй вопрос (Q2) - 4:Медленный мобильный интернет
Берем наши выборки, делаем по каждой из них бутстреп и проверяем долю случаев когда центрированная разница между средними больше изначально заданной, а также генерируем распределние разницы средних.
# Группа A
df4_2 = data_Q2[(data_Q2['Q2'] == '3') | (data_Q2['Q2'] == '4')] # Интересующие нас пользователи
df4_2 = df4_2.drop_duplicates(subset='user_id') # Устранение повторяющихся пользователей внутри групп
df4_2
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 4 | B | 8 | 3 | 179.18564 | 2590.97 | 325.88 | 7053.81 | 1221.02 | 0.80 | 3218 | 51 |
| 3 | 5 | C | 2 | 3 | 351.99208 | 731.61 | 223.54 | 4550.38 | 2336.56 | 1.15 | 1767 | 68 |
| 6 | 9 | C | 1 | 3 | 783.64464 | 1786.99 | 271.77 | 6802.42 | 1837.02 | 0.84 | 1200 | 132 |
| 9 | 10 | C | 3 | 3 | 455.97369 | 610.43 | 81.86 | 1317.76 | 1054.15 | 4.10 | 3350 | 165 |
| 13 | 11 | C | 3 | 3 | 526.08652 | 535.54 | 208.67 | 2621.14 | 2376.50 | 1.46 | 1479 | 88 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2719 | 3102 | B | 8 | 4 | 189.14150 | 2432.18 | 72.80 | 2152.91 | 1410.52 | 0.70 | 2719 | 289 |
| 2722 | 3105 | C | 1 | 3 | 206.28181 | 105.24 | 65.84 | 349.04 | 1035.69 | 3.86 | 2094 | 267 |
| 2727 | 3108 | C | 3 | 4 | 519.96475 | 1045.70 | 44.61 | 4523.66 | 1044.66 | 0.47 | 1468 | 304 |
| 2730 | 3109 | C | 3 | 3 | 171.52629 | 670.32 | 40.94 | 1711.54 | 954.91 | 2.35 | 2780 | 251 |
| 2735 | 3111 | B | 6 | 3 | 827.74515 | 1841.90 | 373.53 | 5675.93 | 2361.88 | 1.21 | 1905 | 202 |
655 rows × 12 columns
# Группа B
df4_3 = data_Q1_Out[data_Q1_Out['Group'] == 'A']
df4_3 = df4_3.drop_duplicates(subset='user_id') # Устранение повторяющихся пользователей внутри групп
df4_3
| user_id | Group | Q1 | Q2 | Total Traffic(MB) | Downlink Throughput(Kbps) | Uplink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Web Page Download Throughput(Kbps) | Downlink TCP Retransmission Rate(%) | Video Streaming xKB Start Delay(ms) | Web Average TCP RTT(ms) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15 | 16 | A | 10 | NaN | 767.54725 | 1729.37 | 107.94 | 2490.93 | 2514.08 | 1.96 | 1660 | 92 |
| 21 | 22 | A | 10 | NaN | 601.06519 | 2135.92 | 120.55 | 7215.26 | 1014.48 | 1.73 | 1649 | 231 |
| 25 | 26 | A | 10 | NaN | 360.59845 | 3743.08 | 250.93 | 5943.66 | 2714.29 | 0.98 | 2029 | 61 |
| 27 | 28 | A | 10 | NaN | 373.37642 | 865.79 | 82.03 | 1913.33 | 1743.42 | 2.62 | 2638 | 122 |
| 28 | 29 | A | 10 | NaN | 232.99499 | 535.05 | 71.64 | 1332.35 | 3421.72 | 1.07 | 3548 | 110 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3091 | 3092 | A | 9 | NaN | 335.35499 | 1767.76 | 104.68 | 10115.44 | 462.42 | 0.38 | 1234 | 164 |
| 3092 | 3093 | A | 10 | NaN | 758.71809 | 1512.61 | 192.21 | 796.91 | 216.96 | 0.87 | 2567 | 160 |
| 3098 | 3099 | A | 10 | NaN | 343.79289 | 3469.50 | 237.13 | 4559.33 | 409.40 | 0.69 | 1725 | 150 |
| 3105 | 3106 | A | 10 | NaN | 424.34855 | 2258.16 | 150.21 | 5623.82 | 3480.14 | 1.56 | 1313 | 109 |
| 3109 | 3110 | A | 10 | NaN | 187.44936 | 590.29 | 186.36 | 3182.83 | 1094.62 | 2.06 | 2195 | 109 |
700 rows × 12 columns
mean_bc_3 = df4_2[['Downlink Throughput(Kbps)']].mean()[0]
mean_bc_3
1718.218458015267
mean_a_3 = df4_3[['Downlink Throughput(Kbps)']].mean()[0]
mean_a_3
1876.4857142857143
mean_diff_3 = np.abs(mean_a_3 - mean_bc_3)
mean_diff_3
158.2672562704472
differences_3 = np.zeros((1, 1500))
confidence = 0.95
count1 = 0
count2 = 0
count3 = 0
count4 = 0
for i in range(0, 1500):
s1 = random.choices(df4_2['Downlink Throughput(Kbps)'].values, k=1000)
s2 = random.choices(df4_3['Downlink Throughput(Kbps)'].values, k=1000)
m1 = np.mean(s1)
m2 = np.mean(s2)
n = len(s1)
se1 = st.sem(s1)
se2 = st.sem(s2)
h1 = se1 * st.t._ppf((1+confidence)/2., n-1)
h2 = se2 * st.t._ppf((1+confidence)/2., n-1)
differences_3[0][i] = m2 - m1
l1, r1, l2, r2 = m1-h1, m1+h1, m2-h2, m2+h2 # Левая и правая граница интервалов
if (l2<l1<r2<r1) or (l1<l2<r2<r1) or (l2<l1<r1<r2) or (l1<l2<r1<r2): #пересечение интервалов
count1 += 1 # Выводим значения интервалов, если они пересекаются
if (m2-m1 > h1) and (m2-m1 > h2) and (m2>m1):
count4 += 1
if (l2<r2<l1<r1):
count2 += 1
if (l1<r1<l2<r2):
count3 += 1
print ("Число случаев, когда дов. интервалы пересекаются -",count1)
print ("Число случаев, когда разница между средними значима при пересечении интервалов -", count4)
print ("Число случаев, когда интервалы не пересекаются и дов. интервал группы A(Q2) меньше, чем группы B(Q1) -", count3)
print('_________________________________________')
print ("Общая доля случаев, когда дов. интервал группы B больше чем группы A -",(count3 + count4) / 1500)
Число случаев, когда дов. интервалы пересекаются - 859 Число случаев, когда разница между средними значима при пересечении интервалов - 673 Число случаев, когда интервалы не пересекаются и дов. интервал группы A(Q2) меньше, чем группы B(Q1) - 641 _________________________________________ Общая доля случаев, когда дов. интервал группы B больше чем группы A - 0.876
((np.abs(differences_3 - np.mean(differences_3)) >= mean_diff_3).sum()) / 1500
# Доля случаев когда центрированная разница больше изначально заданной
0.012
Полученное P-value значимо меньше чем P-value = 0.05 при заданном уровне значимости -> нулевая гипотеза о равенстве средних отклоняется -> Разница есть
Также учитывая общую долю случаев, когда среднее группы B больше группы A (проверка доверительных интервалов), можем сделать вывод, что среднее ГС данной группы пользователей значимо больше
df_diff_3 = pd.DataFrame(differences_3 - np.mean(differences_3)).transpose()
# Датафрейм разницы средних для построения графика
df_diff_3.columns = ['differences']
df_diff_3
| differences | |
|---|---|
| 0 | -82.035064 |
| 1 | -40.811274 |
| 2 | -26.441694 |
| 3 | -107.948144 |
| 4 | 44.754246 |
| ... | ... |
| 1495 | 89.827916 |
| 1496 | 12.514216 |
| 1497 | -9.577074 |
| 1498 | 4.663726 |
| 1499 | 77.936036 |
1500 rows × 1 columns
fig = px.histogram(df_diff_3, nbins = 50,
title='Распределение разницы средних для рассматриваемых групп')
fig.show() # Распределение напоминает нормальное
На основании произведенных расчетов и тестов можем сделать вывод, что показатель Downlink Throughput(Kbps)(средняя скорость «к абоненту»), статистически различается у ГС пользователей ответивших на второй вопрос (Q2) - 3,4 (3: Плохое качество связи в зданиях, тц и т.д. 4: Медленный мобильный интернет) и пользователей, ответивших на первый вопрос (Q1) - 9,10
Так как данный показатель, не отличается внутри групп пользователей ответивших на Q2 - 3 и 4, но статистически различается также между вышеуказанной обобщенной группой и пользователями, полностью довольными качеством связи (у пользователей данной группы значимо выше), можем сделать вывод, что данный показател влияет на удовлетворённость клиентов и ресурсы на работу с ним направить нужно.
Рассмотрим показатели скорости загрузки потокового видео и задержки старта воспроизведения для пользователей с медленной загрузкой видео
data_Q2.groupby(['Q2'])[['Downlink Throughput(Kbps)',
'Video Streaming Download Throughput(Kbps)',
'Video Streaming xKB Start Delay(ms)']] \
.agg(['count', 'mean', 'median']).sort_values(by='Q2') \
.iloc[:, [0, 1, 2, 4, 5, 7, 8]] \
.style.apply(lambda x: ['background: lightblue' if x.name == '5' else '' for i in x], axis=1)
| Downlink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Video Streaming xKB Start Delay(ms) | |||||
|---|---|---|---|---|---|---|---|
| count | mean | median | mean | median | mean | median | |
| Q2 | |||||||
| 1 | 403 | 1787.284045 | 1361.300000 | 5045.347916 | 4576.390000 | 1817.253102 | 1662.000000 |
| 2 | 124 | 1654.466774 | 1095.545000 | 4793.183790 | 4050.375000 | 1847.862903 | 1709.500000 |
| 3 | 480 | 1728.353542 | 1314.740000 | 4817.769083 | 4252.065000 | 1809.033333 | 1664.500000 |
| 4 | 387 | 1661.618786 | 1155.320000 | 4482.688837 | 3915.890000 | 1902.981912 | 1743.000000 |
| 5 | 136 | 1447.946029 | 1108.190000 | 3919.861838 | 3287.100000 | 1929.757353 | 1743.500000 |
| 6 | 452 | 1684.155686 | 1265.225000 | 5047.898628 | 4349.455000 | 1796.794248 | 1671.000000 |
| 7 | 54 | 1753.447778 | 1543.905000 | 4808.978148 | 4223.850000 | 1817.148148 | 1700.500000 |
data_Q1_Out.groupby(['Group'])[['Downlink Throughput(Kbps)',
'Video Streaming Download Throughput(Kbps)',
'Video Streaming xKB Start Delay(ms)']] \
.agg(['count', 'mean', 'median']).iloc[:, [0, 1, 2, 4, 5, 7, 8]].round(2) \
.style.apply(lambda x: ['background: orange' if x.name == 'A' else '' for i in x], axis=1)
| Downlink Throughput(Kbps) | Video Streaming Download Throughput(Kbps) | Video Streaming xKB Start Delay(ms) | |||||
|---|---|---|---|---|---|---|---|
| count | mean | median | mean | median | mean | median | |
| Group | |||||||
| A | 700 | 1876.490000 | 1519.740000 | 5371.520000 | 4821.220000 | 1696.580000 | 1572.500000 |
| B | 547 | 1842.240000 | 1366.610000 | 5260.610000 | 4623.440000 | 1768.550000 | 1655.000000 |
| C | 734 | 1662.220000 | 1293.520000 | 4708.800000 | 4109.820000 | 1855.180000 | 1694.000000 |
# Наблюдения в выборках группы A и ответивших на второй вопрос 5 независимы
data_Q2['Video Streaming Download Throughput(Kbps)'][(data_Q2['Q1'] == 'A') |
(data_Q2['Q2'] == '5')].duplicated().sum()
0
fig = px.scatter(data_Q1_Out, x='Video Streaming xKB Start Delay(ms)',
y='Video Streaming Download Throughput(Kbps)',
color='Group',
title='Зависимость скорости загрузки видео от задержки начала воспроизведения')
fig.show()
fig = px.scatter(data_Q1_Out, x='Downlink Throughput(Kbps)',
y='Video Streaming Download Throughput(Kbps)',
color='Group',
title='Зависимость скорости загрузки видео от скорости "к абоненту"')
fig.show()
fig = px.histogram(data_Q1_Out, x='Video Streaming Download Throughput(Kbps)',
title='Распределение скорости загрузки потокового видео')
fig.show()
Непараметрические критерии используются для следующих переменных:
- для количественных переменных, распределение которых не подчиняется нормальному закону распределения;
Непараметрические критерии могут применяться и в случае нормального распределения. В этом случае они будут иметь только 95%-ую эффективность по сравнению с параметрическими тестами.
Существует большое количество непараметрических тестов, которые можно разделить на три группы:
- критерии для независимых выборок (U критерий Манна-Уитни (при сумме размеров выборок >60 U-статистика аппроксимируется нормальным распределением), критерий Колмогорова-Смирнова для двух выборок, критерий Вальда-Вольфовица, критерий Мозеса, непараметрический дисперсионный анализ Крускала-Уоллиса, медианный критерий, критерий Джонкхира-Терпстры и др.)